#include "database.h"

Database::Database(){
	//CREATING A DUMMY NODE TO EASE THE IMPLEMENTATION
	StartTable = new Table;
	StartTable->next = NULL;  
    Query q(StartTable);
	QueryObj=q;
}

Database::~Database(){
  Table*tblPtr=StartTable;
  while(tblPtr!=NULL){    
	  //Delete all table
    Table*tmp = tblPtr->next;
    Record*recPtr=tblPtr->StartRec;
    while(recPtr!=NULL){  
		//Delete all of the current tables records
      Record*rtmp=recPtr->next;
      delete recPtr;
      recPtr=rtmp;
    }
    tblPtr=tmp;
  }
}

// CREATES A TABLE AFTER OBTAINING INFORMATION FROM THE FILE.
bool Database::CreateTable(string TableName, vector<string>& AttributeName, vector<FieldType>& AttributeType, int PrimaryKeyIndex){
	if(FindTable(TableName) == NULL){
		Table *t = new Table;
		t->TableName = TableName;
		t->AttributeName = AttributeName;
		t->AttributeType = AttributeType;
		t->key = PrimaryKeyIndex;
		InsertTable(t);
		return true;
		//true - table was inserted
	}
	return false;
	//false if the table already existed
}

// CREATES A TABLE AFTER OBTAINING INFORMATION FROM THE 'DEFINE WITH' COMMAND.
void Database::DefineWith(string Define, string Wher){
	string TableName, temp, type;
	stringstream stream;
	char c;
	int Index=0, i=0;
	TableName = QueryObj.RemoveSpace(Define);
	vector<string> Attributes;
	vector<FieldType> Types;
	stream.clear();
	stream.str(Wher);
	//Parses the WITH portion of the command.
	while(!stream.eof()){
		stream.get(c);
		temp.clear();
		while(isspace(c))
			stream.get(c);
		while(!isspace(c)){
			temp+=c;
			stream.get(c);
		}
		if(temp.compare("STRING")==0)
			Types.push_back(StringType);
		else if(temp.compare("INT")==0)
			Types.push_back(IntType);
		else if(temp.compare("FLOAT")==0)
			Types.push_back(FloatType);
		else
			Types.push_back(InvalidType);
		temp.clear();
		while(isspace(c))
			stream.get(c);
		while((c!=',') && (!stream.eof())){
			if(c=='*'){
				Index=i;
				stream.get(c);
			}
			temp+=c;
			stream.get(c);
		}
		temp = QueryObj.RemoveSpace(temp);
		Attributes.push_back(temp);
		i++;
	}
	if(CreateTable(TableName, Attributes, Types, Index)==true)
		cout<<"ALL TABLES WERE CREATED SUCCESSFULLY!"<<endl;
}
	
//INSERTS A TABLE IN THE LINKED LIST.
void Database::InsertTable(Table*t){
  t->next = StartTable->next;
	StartTable->next = t;
}

//THIS FUNCTION RETURNS THE TABLE BEFORE THE TABLE WE'RE LOOKING FOR
Table* Database::FindTable(string t){
	Table *tbl = StartTable;
	while(tbl->next!=NULL){
		if((tbl->next->TableName) == t)
			return tbl;
		tbl = tbl->next;
	}
	return NULL;
}

// DELETES A TABLE FROM THE LINKED LIST.
void Database::DeleteTable(string TableName){
	Table *TablePtr = FindTable(TableName);
	Table *TableTemp = TablePtr->next->next;
	delete TablePtr->next;
	TablePtr->next = TableTemp;
}

// DELETES ALL RECORDS OF A TABLE.
void Database::EmptyTable(string TableName){
	Table *TableTemp = FindTable(QueryObj.RemoveSpace(TableName));
	Record *RecTemp = TableTemp->next->StartRec, *Temp;
	while(RecTemp->next!=NULL){
		Temp = RecTemp->next;
		RecTemp->next = RecTemp->next->next;
		delete Temp;
	}
}

// INSERTS A RECORD AFTER OBTAINING INFORMATION FROM THE FILE.
bool Database::InsertRecord(string TableName, vector<void*> &toInsert){

  Record*copyRec=new Record;
  for(int i=0;i<int((toInsert).size());++i){
    (*copyRec).RecordPtr.push_back(toInsert.at(i));
  }
	Table *before = FindTable(TableName);
	if(before == NULL)
		return false;
	Record *tmp = before->next->StartRec->next; 
	before->next->StartRec->next=copyRec;      
	copyRec->next=tmp;                         
	return true;
}

// INSERTS A RECORD AFTER OBTAINING INFORMATION FROM THE 'INSERT WITH' COMMAND.
void Database::InsertRecord(string TableName, string With){
	char c;
	vector<void*> toInsert;
	int i=0;
	string value, temp;
	Table *TablePtr = FindTable(TableName);
	if(TablePtr==NULL){
		cout<<"Table does not exist";
		return;
	}
	TablePtr=TablePtr->next;
	stringstream stream;
	stream.str(With);
	//Parses WITH portion of the command.
	while(!stream.eof()){
		stream.get(c);
		value.clear();
		temp.clear();
		while(isspace(c))
			stream.get(c);
		if(c=='\''){
			stream.get(c);
			while(c!='\''){
				value+=c;
				stream.get(c);
			}
			stream.get(c);
			while((c!=',')&&(!stream.eof()))
				stream.get(c);
			string *temp = new string;
			*temp = value;
			toInsert.push_back((void*)temp);
			i++;
		}
		else{
			while((c!=',')&&(!stream.eof())){
				value+=c;
				stream.get(c);
			}
			value = QueryObj.RemoveSpace(value);
			if(TablePtr->AttributeType.at(i) == IntType){
				int *temp = new int;
				(*temp)=atoi(value.c_str());
				toInsert.push_back((void*)temp);
				i++;
			}
			else if(TablePtr->AttributeType.at(i) == FloatType){
				float *temp = new float;
				if(!value.empty())
					(*temp) = float(atof(value.c_str()));
				else
					*temp = 0.0;
				toInsert.push_back((void*)temp);
				i++;
			}
		}
	}
	//Call function to insert record after generating the record.
	if(InsertRecord(TableName, toInsert)==true)
		cout<<"THE VALUES WERE INSERTED INTO THE TABLE SUCCESSFULLY!"<<endl;
	else
		cerr<<"ERROR IN INPUT!"<<endl;
}

// DELETES ALL RECORDS OBTAINED FROM THE 'FROM' CLAUSE AND 'WHERE' CLAUSE OF THE 'DELETE FROM WHERE' COMMAND.
void Database::DeleteRecord(string from, string wher){
	vector<Table*> TableList;
	vector<Record*> RecordList;
	Record *TempRec;
	TableList = QueryObj.From(from);
	RecordList = QueryObj.Where(wher, TableList);
	cout<<RecordList.size();
	for(int i=int(RecordList.size()-1);i>=0;i--){
		TempRec = RecordList.at(i)->next;
		RecordList.at(i)->next = RecordList.at(i)->next->next;
		delete TempRec;
	}	
}

//MODIFIES THE RECORDS(CHOSEN BY THE 'WHERE' CONDITION) OF A TABLE. 
void Database::UpdateTable(string Update, string Set, string Wher){
	stringstream stream;
	vector<Table*> ReqTable, PassTable; 
	char c;
	int i, j, flag=0;
	Table *TablePtr;
	vector<int> pos;
	vector<Record*> ReqRecord;
	vector<string> ReqAttribute;
	vector<string> ReqValue;
	string temp;
	string TableName = QueryObj.RemoveSpace(Update);
	TablePtr = FindTable(TableName);
	if(TablePtr == NULL){
		cout<<"Table does not exist"<<endl;
		return;
	}
	ReqTable.push_back(TablePtr);
	PassTable = ReqTable;
	PassTable.at(0) = PassTable.at(0)->next;
	ReqRecord = QueryObj.Where(Wher, PassTable);		
	//This part of the code parses the SET clause.
	//SET <AttributeName> = <Value>, ...
	cout<<ReqRecord.size()<<endl;
	stringstream tstream;	
	stream.clear();
	temp.clear();
		stream.str(Set);
		stream.get(c);
	while(!stream.eof()){
		while(c!='='){
			temp+=c;
			stream.get(c);
		}
						
		temp = QueryObj.RemoveSpace(temp);
		cout<<temp;
		ReqAttribute.push_back(temp);
		temp.clear();
		tstream.clear();
		
		stream.get(c);
		flag=0;
		for(i=0;i<int(((ReqTable.at(0))->next->AttributeName).size());i++){
			if( (ReqAttribute.back()).compare(((ReqTable.at(0))->next->AttributeName).at(i)) == 0){
				pos.push_back(i);
				flag=1;
			}
		}
		if(flag==0){
			cout<<"Attribute does not exist."<<endl;
			return;
		}
		//Parse the STRING value separately. Search for single quotes.
		if(((ReqTable.at(0))->next->AttributeType).at(pos.back()) == StringType){
			while(c!='\'')
				stream.get(c);
			stream.get(c);
			while(c!='\''){
				temp+=c;
				stream.get(c);
			}
			stream.get(c);
			ReqValue.push_back(temp);
		}
		//Parse INT and FLOAT separately.
		else{
			
			while(!isdigit(c))
				stream.get(c);
			cout<<c<<endl;
			while((c!=' ') && (c!=',') && (!stream.eof())){
				temp+=c;
				stream.get(c);
			}
			ReqValue.push_back(temp);
		}
		temp.clear();
		stream.get(c);
	}
	flag = 0;

	//converts the values to void pointers and inserts them in the appropriate position.
	for(i=0;i<int(ReqRecord.size());i++){
		for(j=0;j<int(pos.size());j++){
			
			if(((ReqTable.at(0))->next->AttributeType).at(pos.at(j)) == StringType){
				string *VoidTemp = new string;
				*VoidTemp = ReqValue.at(j);
				flag=1;
				((ReqRecord.at(i))->next->RecordPtr).at(pos.at(j)) = (void*)VoidTemp;
			}
			else if(((ReqTable.at(0))->next->AttributeType).at(pos.at(j)) == FloatType){
				float *VoidTemp = new float;
				istringstream Stream(ReqValue.at(j));
				Stream >> (*VoidTemp);
				flag=1;
				((ReqRecord.at(i))->next->RecordPtr).at(pos.at(j)) = (void*)VoidTemp;
			}
			else if(((ReqTable.at(0))->next->AttributeType).at(pos.at(j)) == IntType){
				int *VoidTemp = new int;
				(*VoidTemp)=atoi(ReqValue.at(j).c_str());
				flag=1;
				((ReqRecord.at(i))->next->RecordPtr).at(pos.at(j)) = (void*)VoidTemp;
			}
		}
	}
	if(flag == 1){
		PrintRecordInfo(TableName,cout);
		cout<<endl<<"The Records were updated!"<<endl;
	}
	else
		cout<<"No Records matched your condition!"<<endl;
}

//DISPLAY INFORMATION ABOUT A TABLE.
void Database::PrintTableInfo(string TableName)const{
  Table*ptr=StartTable->next;
  while(ptr!=NULL){
    if(ptr->TableName==TableName){
      for(int x=0;x<int(ptr->AttributeName.size());++x){
        cerr << (ptr->AttributeName).at(x) << " " << (ptr->AttributeType).at(x) << endl;
      }
      return;
    }
    ptr=ptr->next;
  }
}

//DISPLAY THE RECORDS OF A TABLE.
void Database:: PrintRecordInfo(string TableName, ostream& sout){
	Table *TableTemp = StartTable->next;
	int i;
	while(TableTemp!=NULL){								//Get the table to output
		if(TableName.compare(TableTemp->TableName)==0)
			break;
		TableTemp = TableTemp->next;
	}

	if(TableTemp == NULL){								//If there is nothing, just say there is nothing to print.
		sout << "Table " << TableName << " does not exist";
		return;
	}

	sout << TableTemp->TableName << endl;				//Print the table name

	for(i=0;i<int((TableTemp->AttributeName).size());i++){	//Print the attribute info
		sout << (TableTemp->AttributeName).at(i) << "  \t";
	}
	sout << endl;

	Record *RecordTemp = TableTemp->StartRec->next;
	
	//Convert a void pointer to its corresponding type in the table and display it.
	while(RecordTemp!=NULL){
		for(i=0;i<int((TableTemp->AttributeType).size());i++){
			if((TableTemp->AttributeType).at(i) == StringType){
				sout << *(string*)(RecordTemp->RecordPtr).at(i);
			}else if((TableTemp->AttributeType).at(i) == FloatType){
				sout << *(float*)(RecordTemp->RecordPtr).at(i);
			}else if((TableTemp->AttributeType).at(i) == IntType){
				sout << *(int*)(RecordTemp->RecordPtr).at(i);
			}
			sout << "\t";
		}
		sout << endl;
		RecordTemp = RecordTemp->next;
	}
}

//DISPLAY RECORDS OF ALL TABLES WITH THE TABLENAME.
void Database::PrintAllTableRecord(ostream&sout){
	Table *TableTemp = StartTable->next;
	while(TableTemp!=NULL){
		PrintRecordInfo(TableTemp->TableName,sout);
		TableTemp = TableTemp->next;
	}
}

//DISPLAY INFORMATION ABOUT ALL TABLES.
void Database::PrintAll()const{ //For Debugging only
  Table *ptr = StartTable->next;
  while(ptr!=NULL){
    cerr << "Table Name: " << ptr->TableName << endl;
    for(int x=0;x< int(ptr->AttributeName.size());++x){
      cerr << (ptr->AttributeName).at(x) << " " << (ptr->AttributeType).at(x) << endl;
    }
	cout<<endl;
    ptr = ptr->next;

  }
}

string Database::TypeToString(FieldType type){
	string str;
	switch(type){
		case IntType: str="INT";  return str;
					break;
		case FloatType: str = "FLOAT"; return str;
					break;
		case StringType: str = "STRING"; return str;
					break;
		default: str="INVALID"; return str;
	}
}

//SAVE TABLE INFORMATION TO FILE
void Database::SaveTable(string FileName){
	ofstream fout;
	Table *TablePtr;
	int i=0;
	TablePtr = StartTable->next;
	fout.open(FileName.c_str());
	while(TablePtr!=NULL){
		fout << TablePtr->TableName << ", " << TablePtr->AttributeName.size() << endl;
		for(i=0;i<int(TablePtr->AttributeName.size());i++){
			fout << TypeToString(TablePtr->AttributeType.at(i)) << " ";
			if(i==TablePtr->key)
				fout<<"*";
			fout << TablePtr->AttributeName.at(i)<<endl;
		}
		TablePtr=TablePtr->next;
	}
	cout<<FileName<<" WAS CREATED WITH INFORMATION OF ALL TABLES."<<endl;
}

//SAVE RECORD INFORMATION OF ALL TABLES TO FILE
void Database::SaveData(string FileName){
	ofstream fout;
	Table *TableTemp;
	int i=0,j=0,k=0;
	Record *RecordTemp, *Temp;
	fout.open(FileName.c_str());
	TableTemp = StartTable->next;
	while(TableTemp!=NULL){
		j=0;
		RecordTemp = TableTemp->StartRec->next;
		fout << TableTemp->TableName << ", ";
		Temp=RecordTemp;
		while(Temp!=NULL){
			j++;
			Temp=Temp->next;
		}
		fout << j <<endl;
		while(RecordTemp!=NULL){
			for(i=0;i<int((TableTemp->AttributeType).size());i++){
				if((TableTemp->AttributeType).at(i) == StringType){
					string *StringTemp = new string;
					StringTemp = (string*)(RecordTemp->RecordPtr).at(i);
					fout<<*StringTemp;
				}
				else if((TableTemp->AttributeType).at(i) == FloatType){
					float *FloatTemp = new float;
					FloatTemp = (float*)(RecordTemp->RecordPtr).at(i);
					fout<<*FloatTemp;
				}
				else if((TableTemp->AttributeType).at(i) == IntType){
					int *IntTemp = new int;
					IntTemp = (int*)(RecordTemp->RecordPtr).at(i);
					fout<<*IntTemp;
				}
				if(i!=(TableTemp->AttributeName.size()-1))
					fout<<", ";
			}
			fout<<endl;
			RecordTemp = RecordTemp->next;
		}
		TableTemp = TableTemp->next;
	}
	cout<<FileName<<" WAS CREATED WITH ALL RECORD INFORMATION OF ALL TABLES."<<endl;
}

//RETURNS NUMBER OF TABLES IN THE DATABASE.
int Database::NumTables()const{ //for debugging only
  Table*ptr=StartTable->next;
  int count=0;
  while(ptr!=NULL){
    ++count;
    ptr=ptr->next;
  }
  return count;
}

//READS THE SCHEMA FILE BASED ON THE FORMAT GIVEN AND CREATES THE TABLES.
bool Database::ReadTableFormat(ifstream& fin){
  string Temp = ReadStringFromFile(fin);
	TableName.push_back(Temp);
  
  int value;
  static int k=0;
  if(TableName.at(TableName.size()-1)==""){
    return false;
  }
  fin >> value;
  nAttribute.push_back(value);
  int primaryKey=0;
  names.erase(names.begin(),names.end());
  types.push_back(vector<FieldType>());
  for(int x=0;x<nAttribute.at(nAttribute.size()-1);++x){
    string t;
    fin >> t;
    types[k].push_back(getFieldType(t));
    char *line=new char[80];
    int len=80;
    fin.getline(line,len);
    char*first=line+1;
    if(line[1]=='*'){
      primaryKey=x;
      first=line+2;
    }
    string l(first);
    delete []line;
    names.push_back(l);
  }

  if(CreateTable(TableName[k],names,types[k],primaryKey) == true){
	  k++;
	  return true;
  }
  else
	  return false;
}

//READS THE DATA FILE BASED ON THE FORMAT GIVEN AND CREATES A RECORD
bool Database::ReadRecordFormat(ifstream& fin){
  string TableTemp = ReadStringFromFile(fin);
  int k=0, i, j, flag=0;
  if(TableTemp == ""){
	  return false;
  }
  int nValues=0;
  //No. of tuples.
  fin >> nValues;
  fin.get();
  //One record of information.
  vector<void*> toInsert;
  for(i=0;i<int(TableName.size());i++){
	  if(TableTemp.compare(TableName.at(i)) == 0){
		  k=i; break;
	  }
  }
  for(i=0; i < nValues ; ++i){
	  toInsert.erase(toInsert.begin(), toInsert.end());
	  for(j=0; j < nAttribute.at(k); j++){
	    if(types[k][j] == IntType){
        int *temp=new int;
        string str=ReadStringFromFile(fin);
        (*temp)=atoi(str.c_str());
		    toInsert.push_back((void*)temp);
	    }else if(types[k][j] == FloatType){
		    float *temp=new float;
		    float d=0.0;
		    string strValue = ReadStringFromFile(fin);
		    if(!strValue.empty()){
			    *temp = (float)(atof(strValue.c_str()));
        }else{
			    *temp = 0.0;
        }
		    toInsert.push_back((void*)temp);
	    }else if(types[k][j] == StringType){
		    string *temp=new string;
		    (*temp)=ReadStringFromFile(fin);
		    toInsert.push_back((void*)temp);
	    }
	  }
	  if(InsertRecord(TableTemp, toInsert) == false){
		  flag = 1;
		  break;
	  }
  }
  if(flag == 0)
	  return true;
  else
	  return false;
}

//INPUTS A FILENAME AND CALLS THE FUNCTION THAT READS THE SCHEMA FILE TO CREATE THE TABLES.
void Database::DefineDatabaseFromFile(string fname){
  ifstream FinDatabase;
	FinDatabase.open(fname.c_str());
	if(!FinDatabase.is_open()){
	  cout << "File does not exist." << endl;
		FinDatabase.clear();
		return;
	}
  while(ReadTableFormat(FinDatabase));
  FinDatabase.close();
  cout << "Successfully read " << fname << ".\n";
}

//INPUTS A FILENAME AND CALLS THE FUNCTION THAT READS THE DATA FILE TO CREATE THE RECORDS.
void Database::InsertDataFromFile(string fname){
  ifstream FinData;
	FinData.open(fname.c_str());
	if(!FinData.is_open()){
		cout << "File does not exist."<<endl;
		return;
	}
  while(ReadRecordFormat(FinData) == true);
  FinData.close();
  cout << "Successfully read " << fname << ".\n";
}

//THIS FUNCTION REMOVES SURROUNDING SPACES OF A STRING OBTAINED FROM A FILE.
//THIS IS USED TO READ THE SCHEMA AND DATA FILES.
//IT RETAINS THE SPACE BETWEEN TWO WORDS (AS IN ATTRIBUTE NAMES)
string Database::ReadStringFromFile(ifstream&fin){
	
  string ret="";
  int flag=0;
  char c,t;
  
  do{
    fin.get(c);

	if(c == ' '){
		t = c;
		fin.get(c);
		//This part checks if the next character after space is an alphabet or digit or comma.
		//If it is, then the space is retained.
		if((isalpha(c)||isdigit(c)) && (flag==1) && (c!=',')){
			ret+=t;
			flag=1;
		}
	}
	if(fin.eof())
		return "";
	if((!isspace(c)) && (flag == 0) )
		flag = 1;						//When an alphabet is encountered flag is set.
	if((c!=',') && (c!='\n') && (flag == 1) && (c!=' ')){
      ret+=c;
	}
  
  }while((c!=',') && (c!='\n'));
  
  return ret;
}

FieldType Database::getFieldType(string str){
	if(str=="FLOAT")
		return FloatType;
  if(str=="INT")
		return IntType;
	if(str=="STRING")
		return StringType;
	return InvalidType;
}